package errorDetection;

import java.io.IOException;
import java.net.ConnectException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.PortUnreachableException;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.util.ArrayList;
import java.util.Timer;
import java.util.TimerTask;

import server.SuperServer;
import centralRepository.CentralRepository;
import centralRepository.ServerObject;
import centralRepository.ServerObjectImpl;
import centralRepository.ServerObjectImpl.Status;

public class ErrorDetection {

	private ServerObjectImpl server;
	private static ArrayList<ServerObject> currentState, healthyState;

	/**
	 * starts the error detection module for a specified server instance
	 * 
	 * @param server
	 *            - the server you want the error detection running for
	 */
	public ErrorDetection(ServerObjectImpl server) {
		this.server = server;
		Thread listenerThread = new Thread(new Runnable() {
			public void run() {
				try {
					startListener();
				} catch (NumberFormatException e) {
					System.out.println("PARSING EXCEPTION IN START LISTENER");
				} catch (IOException e) {
					System.out.println("IO EXCEPTION IN START LISTENER");
				}
			}
		});
		listenerThread.start();

		Thread errorDetectorThread = new Thread(new Runnable() {
			public void run() {
				try {
					runErrorDetection();
				} catch (NumberFormatException e) {
					System.out.println("PARSING EXCEPTION IN ERROR DETECTION");
				}
			}
		});
		errorDetectorThread.start();
	}

	private void runErrorDetection() {

		Timer timer = new Timer();

		// SET A TIMER TO RUN THE ALGORITHM EVERY 1 min
		timer.scheduleAtFixedRate(new TimerTask() {
			@Override
			public void run() {

				// ErrorDetection.healthyState =
				// CentralRepository.getRunningReplicas(server);
				// ErrorDetection.currentState =
				// get NS
				ArrayList<ServerObjectImpl> otherRunningServersInList = null;
				otherRunningServersInList = CentralRepository
						.getOtherRunningReplicasInList(server);

				ArrayList<ServerObject> otherRunningServers = null;
				if (server != null) {
					System.out.println("RUNNING ERROR DETECTION FOR : "
							+ server.getName());
					otherRunningServers = CentralRepository
							.getOtherRunningReplicas(server);
				}

				// loop through the naming service and find other servers with
				// the same name
				if (otherRunningServers != null) {
					for (ServerObjectImpl s : otherRunningServersInList) {
						// try to connect to them;
						DatagramSocket clientSocket = null;

						boolean isRunning = false;

						try {
							for (ServerObject s2 : otherRunningServers) {
								if (s2.getId().equals(s.getId())) {
									isRunning = true;
								}
							}

							if (!isRunning) {
								doHandleCrash(s);
							}
							// get from bridge class
							clientSocket = new DatagramSocket(Integer
									.parseInt(s.getEdPortNum()));

							InetAddress IPAddress = InetAddress.getByName(s
									.getHostname());// needs to be set
													// dynamically
							byte[] sendData = new byte[1024];
							byte[] receiveData = new byte[1024];
							String testSendData = server.getName(); // SENDING
																	// TEST DATA
																	// to get
																	// server
																	// reply via
																	// UDP
							sendData = testSendData.getBytes();
							DatagramPacket sendPacket = new DatagramPacket(
									sendData, sendData.length, IPAddress,
									Integer.parseInt(s.getEdPortNum()));
							clientSocket.setSoTimeout(5 * 1000);// 5 secs delay
							clientSocket.send(sendPacket);
							DatagramPacket receivePacket = new DatagramPacket(
									receiveData, receiveData.length);
							clientSocket.receive(receivePacket);
							System.out.println("RECEIVED PACKET FOR : "
									+ s.getName() + " IN SERVER "
									+ server.getName());
						} catch (ConnectException e) {

						} catch (SocketException e) {

						} catch (SocketTimeoutException e) {

						} catch (IOException e) {

						} finally {
							if(clientSocket != null) {
								clientSocket.close();
							}
						}
					}
				}
			}
		}, 1 * 5 * 1000, 1 * 5 * 1000);
	}

	private void startListener() throws NumberFormatException, IOException {
		System.out.println("STARTING ERROR DETECTION LISTENER FOR : "
				+ server.getName());
		DatagramSocket serverSocket = new DatagramSocket(
				Integer.parseInt(server.getEdPortNum()));// listen at this port
		// number
		byte[] receiveData = new byte[1024];
		byte[] sendData = new byte[1024];

		while (true) {
			DatagramPacket receivePacket = new DatagramPacket(receiveData,
					receiveData.length);
			serverSocket.receive(receivePacket);
			String name = new String(receivePacket.getData());// here we get the
			// sent info
			InetAddress IPAddress = receivePacket.getAddress();
			int port = receivePacket.getPort();
			sendData = name.getBytes();
			DatagramPacket sendPacket = new DatagramPacket(sendData,
					sendData.length, IPAddress, port);
			serverSocket.send(sendPacket);
		}

	}

	private void doHandleCrash(ServerObjectImpl s) {
		s.setStatus(Status.DOWN.toString());
		// replace the DOWN server with the waiting server and set status to
		// running
		ServerObject waitingServer = null;

		System.out
				.println("GETTING THE WAITING PROCESSES AND BRINGING IN A REPLACEMENT");
		ArrayList<ServerObject> objs = CentralRepository.getWaitingProcesses(server);
		if(objs.size() > 0) {
			waitingServer = objs.get(0);
			
			System.out.println("A REPLACEMENT WAS FOUND : "
					+ waitingServer.getName());
			waitingServer.setStatus("RUNNING");
			waitingServer.setLeader(true);
			// if wasLeader run leader election algorithm
			// run leader algorithm
			// start leader election and error detector for the new server
			new SuperServer(waitingServer);
		}
	}

}
